namespace Guia.Elements
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.Contracts;
    using System.Linq;
    using System.Windows.Automation;
    using Guia.Core;
    using Guia.Locator;

    /// <summary>
    /// This class represents a Calendar.
    /// </summary>
    public class Calendar : UiElement
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="Calendar"/> class.
        /// </summary>
        /// <param name="automationElement">
        /// The automation element.
        /// </param>
        public Calendar(AutomationElement automationElement)
            : base(automationElement)
        {
        }

        /// <summary>
        /// The possible views of the Calnedar control.
        /// </summary>
        public enum ECalendarView
        {
            /// <summary>
            /// Shows one Month.
            /// </summary>
            MONTH = 0,

            /// <summary>
            /// Shows one Year.
            /// </summary>
            YEAR,

            /// <summary>
            /// Shows one Decade.
            /// </summary>
            DECADE
        }

        /// <summary>
        /// Gets the default-condition to find a control by its type.
        /// </summary>
        public static SearchCondition ControlTypeCondition
        {
            get { return SearchCondition.ByControlType(ControlType.Calendar); }
        }

        /// <summary>
        /// Gets or sets the View.
        /// </summary>
        /// <exception cref="GuiaException">
        /// </exception>
        public ECalendarView View
        {
            get
            {
                int viewId = this.MultipleViewPattern.Current.CurrentView;
                ECalendarView currentView;
                switch (viewId)
                {
                    case 0:
                        currentView = ECalendarView.MONTH;
                        break;
                    case 1:
                        currentView = ECalendarView.YEAR;
                        break;
                    case 2:
                        currentView = ECalendarView.DECADE;
                        break;
                    default:
                        throw new GuiaException("Invalid View");
                }

                return currentView;
            }

            set
            {
                if (value != this.View)
                {
                    int viewId = 0;
                    switch (value)
                    {
                        case ECalendarView.MONTH:
                            viewId = 0;
                            break;
                        case ECalendarView.YEAR:
                            viewId = 1;
                            break;
                        case ECalendarView.DECADE:
                            viewId = 2;
                            break;
                    }

                    this.MultipleViewPattern.SetCurrentView(viewId);
                }
            }
        }

        /// <summary>
        /// Gets the shown HeaderValue.
        /// </summary>
        public string HeaderValue
        {
            get
            {
                return this.Get<Button>("PART_HeaderButton").Name;
            }
        }

        /// <summary>
        /// Gets the selected dates.
        /// Important: It is only possible to return the selected dates of the current view! Selected dates, that are not visible right now, are not returned. This is because of a lack in the implementation of UIA.
        /// </summary>
        public DateTime[] SelectedDates
        {
            get
            {
                List<DateTime> selectedDates = new List<DateTime>();
                AutomationElement[] selection = this.SelectionPattern.Current.GetSelection();
                foreach (AutomationElement element in selection)
                {
                    UiElement uiElement = new UiElement(element);
                    selectedDates.Add(DateTime.Parse(uiElement.Name));
                }

                return selectedDates.ToArray();
            }
        }

        /// <summary>
        /// Gets the MultipleViewPattern.
        /// </summary>
        private MultipleViewPattern MultipleViewPattern
        {
            get
            {
                return this.GetPattern<MultipleViewPattern>();
            }
        }

        /// <summary>
        /// Gets the SelectionPattern.
        /// </summary>
        private SelectionPattern SelectionPattern
        {
            get
            {
                return this.GetPattern<SelectionPattern>();
            }
        }

        /// <summary>
        /// Navigates to the next Month/Year/Decade
        /// </summary>
        public void Next()
        {
            Button nextButton = this.Get<Button>("PART_NextButton");
            nextButton.Invoke();
        }

        /// <summary>
        /// Navigates to the previous Month/Year/Decade
        /// </summary>
        public void Previous()
        {
            Button prevButton = this.Get<Button>("PART_PreviousButton");
            prevButton.Invoke();
        }

        /// <summary>
        /// Selects a single date.
        /// </summary>
        /// <param name="date">
        /// The date to select.
        /// </param>
        /// <exception cref="OperationNotSupportedException">
        /// </exception>
        public void SetSelectedDate(DateTime date)
        {
            try
            {
                CalendarDayButton dayButton = this.GetCalendarDayButton(date);
                dayButton.Select();
            }
            catch (Exception ex)
            {
                throw new OperationNotSupportedException(ex.Message);
            }
        }

        /// <summary>
        /// Adds a particular date to the selection. This works only with Calendars which allow the selection of multiple ranges.
        /// </summary>
        /// <param name="date">
        /// The date to add to the selection.
        /// </param>
        /// <exception cref="OperationNotSupportedException">
        /// </exception>
        public void AddDateToSelection(DateTime date)
        {
            try
            {
                CalendarDayButton dayButton = this.GetCalendarDayButton(date);
                dayButton.AddToSelection();
            }
            catch (Exception ex)
            {
                throw new OperationNotSupportedException(ex.Message);
            }
        }

        /// <summary>
        /// Unselects a particular date.
        /// </summary>
        /// <param name="date">
        /// The date to unselect.
        /// </param>
        /// <exception cref="OperationNotSupportedException">
        /// </exception>
        public void UnselectDate(DateTime date)
        {
            try
            {
                CalendarDayButton dayButton = this.GetCalendarDayButton(date);
                dayButton.Unselect();
            }
            catch (Exception ex)
            {
                throw new OperationNotSupportedException(ex.Message);
            }
        }

        /// <summary>
        /// Returns true if a particular date is selected, else otherwise.
        /// </summary>
        /// <param name="date">
        /// The date to check.
        /// </param>
        /// <returns>
        /// True if the date is selected, else otherwise.
        /// </returns>
        /// <exception cref="OperationNotSupportedException">
        /// </exception>
        public bool IsDateSelected(DateTime date)
        {
            try
            {
                CalendarDayButton dayButton = this.GetCalendarDayButton(date);
                return dayButton.IsSelected;
            }
            catch (Exception ex)
            {
                throw new OperationNotSupportedException(ex.Message);
            }
        }

        /// <summary>
        /// Selects a range of Dates.
        /// </summary>
        /// <param name="startDate">
        /// The start date.
        /// </param>
        /// <param name="endDate">
        /// The end date.
        /// </param>
        public void SelectRange(DateTime startDate, DateTime endDate)
        {
            Contract.Requires<ArgumentException>(endDate >= startDate, "endDate must not be lower than the startDate");
            List<DateTime> dateRange = new List<DateTime>();
            for (DateTime currentDate = startDate; currentDate <= endDate; currentDate = currentDate.AddDays(1))
            {
                dateRange.Add(currentDate);
            }

            this.SelectRange(dateRange);
        }

        /// <summary>
        /// Selects a range of dates.
        /// </summary>
        /// <param name="dateRange">
        /// The date range.
        /// </param>
        /// <exception cref="OperationNotSupportedException">
        /// </exception>
        public void SelectRange(IEnumerable<DateTime> dateRange)
        {
            try
            {
                bool firstSelected = false;
                foreach (DateTime date in dateRange)
                {
                    if (!firstSelected)
                    {
                        CalendarDayButton dayButton = this.GetCalendarDayButton(date);
                        dayButton.Select();
                        firstSelected = true;
                    }
                    else
                    {
                        CalendarDayButton dayButton = this.GetCalendarDayButton(date);
                        dayButton.AddToSelection();
                    }
                }
            }
            catch (Exception ex)
            {
                throw new OperationNotSupportedException(ex.Message);
            }
        }

        /// <summary>
        /// Adds a range of dates to the selection.
        /// </summary>
        /// <param name="startDate">
        /// The start date.
        /// </param>
        /// <param name="endDate">
        /// The end date.
        /// </param>
        public void AddRangeToSelection(DateTime startDate, DateTime endDate)
        {
            Contract.Requires<ArgumentException>(endDate >= startDate, "endDate must not be lower than the startDate");
            List<DateTime> dateRange = new List<DateTime>();
            for (DateTime currentDate = startDate; currentDate <= endDate; currentDate = currentDate.AddDays(1))
            {
                dateRange.Add(currentDate);
            }

            this.AddRangeToSelection(dateRange);
        }

        /// <summary>
        /// Adds a range of dates to the selection.
        /// </summary>
        /// <param name="dateRange">
        /// The date range.
        /// </param>
        /// <exception cref="OperationNotSupportedException">
        /// </exception>
        public void AddRangeToSelection(IEnumerable<DateTime> dateRange)
        {
            try
            {
                foreach (DateTime date in dateRange)
                {
                    CalendarDayButton dayButton = this.GetCalendarDayButton(date);
                    dayButton.AddToSelection();
                }
            }
            catch (Exception ex)
            {
                throw new OperationNotSupportedException(ex.Message);
            }
        }

        /// <summary>
        /// Navicates to a specific month of a particular year.
        /// </summary>
        /// <param name="month">
        /// The month of the year to nativate to.
        /// </param>
        /// <param name="year">
        /// The year to navigate to.
        /// </param>
        /// <exception cref="ArgumentOutOfRangeException">This exception is thrown if month is not between 1 and 12</exception>
        public void NavigateToMonth(int month, int year)
        {
            Contract.Requires<ArgumentOutOfRangeException>(month >= 1 && month <= 12);
            DateTime navMonth = new DateTime(year, month, 1);
            this.NavigateToMonth(navMonth);
        }

        /// <summary>
        /// Navicates to a specific month of a particular year.
        /// </summary>
        /// <param name="date">
        /// The date to navigate.
        /// </param>
        public void NavigateToMonth(DateTime date)
        {
            try
            {
                // TODO: Use different views for more effective browsing
                this.View = ECalendarView.MONTH;
                DateTime currentVisDate;
                do
                {
                    currentVisDate = DateTime.Parse(this.HeaderValue);
                    while (currentVisDate.Year < date.Year)
                    {
                        this.Next();
                        currentVisDate = DateTime.Parse(this.HeaderValue);
                    }

                    while (currentVisDate.Year > date.Year)
                    {
                        this.Previous();
                        currentVisDate = DateTime.Parse(this.HeaderValue);
                    }

                    if (currentVisDate.Month < date.Month)
                    {
                        this.Next();
                    }
                    else if (currentVisDate.Month > date.Month)
                    {
                        this.Previous();
                    }
                }
                while (currentVisDate.Month != date.Month || currentVisDate.Year != date.Year);
            }
            catch (ElementNotEnabledException ex)
            {
                throw new OperationNotSupportedException(ex.Message);
            }
        }

        /// <summary>
        /// Gets a specific CalendarDayButton.
        /// </summary>
        /// <param name="date">
        /// The date of the button.
        /// </param>
        /// <returns>
        /// The CalendarDayButtion for the date.
        /// </returns>
        private CalendarDayButton GetCalendarDayButton(DateTime date)
        {
            string buttonName = date.ToLongDateString();
            this.NavigateToMonth(date);
            CalendarDayButton dayButton = this.GetByName<CalendarDayButton>(buttonName);
            return dayButton;
        }
    }
}